﻿using System;
using System.Collections.Generic;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Lib.Services;
using NGinnBPM.Lib.Schema;
using System.Text;
using System.IO;
using NLog;
using NGinnBPM.MessageBus;
using NGinnBPM.Lib.Messages;

namespace NGinnBPM.Services
{
    /// <summary>
    /// Package repository on a disk
    /// </summary>
    public class FSProcessPackageRepository : MarshalByRefObject, IProcessPackageRepository
    {
        private Dictionary<string, IProcessPackageStore> _packageStores = new Dictionary<string, IProcessPackageStore>();
        private Logger log = LogManager.GetCurrentClassLogger();
        private bool _refreshData = true;

        public FSProcessPackageRepository()
        {
        }

        public string BaseDirectory { get; set; }
        
        public IProcessScriptManager ScriptManager { get; set; }
        

        public IMessageBus MessageBus { get; set; }

        protected IProcessPackageStore GetPackageStore(string name)
        {
            lock (this)
            {
                if (_refreshData)
                {
                    ScanForPackages();
                    _refreshData = false;
                }
                if (_packageStores.ContainsKey(name))
                {
                    bool reload = false;
                    string pkgDir = Path.Combine(BaseDirectory, name);
                    string file = Path.Combine(pkgDir, "reload.me");
                    if (File.Exists(file))
                    {
                        try
                        {
                            File.Delete(file);
                            reload = true;
                        }
                        catch(Exception) {};
                    }
                    if (reload)
                    {
                        try
                        {
                            log.Info("Detected reload.me file in package {0}. Reloading package", name);
                            ReloadPackage(name);
                        }
                        catch (Exception ex)
                        {
                            log.Error("Failed to reload package {0}: {1}", name, ex);
                            DiagnosticEvent de = new DiagnosticEvent();
                            de.Category = DiagnosticEvent.EventCategory.Error;
                            de.Message = string.Format("Error loading package {0}: {1}", name, ex.Message);
                            de.Source = "FSProcessPackageRepository";
                            MessageBus.Notify(de);
                        }
                    }
                }
            }
            IProcessPackageStore ret;
            return _packageStores.TryGetValue(name, out ret) ? ret : null;
        }

        #region IProcessPackageRepository Members

        public IList<string> PackageNames
        {
            get
            {
                lock (this)
                {
                    if (_refreshData)
                    {
                        ScanForPackages();
                        _refreshData = false;
                    }
                }
                return new List<string>(_packageStores.Keys);
            }

        }

        public NGinnBPM.Lib.Schema.PackageDefinition GetPackage(string name)
        {
            try
            {
                IProcessPackageStore pst = GetPackageStore(name);
                if (pst == null)
                    throw new ApplicationException("Package not found: " + name);
                return pst.GetPackageDefinition();
            }
            catch (Exception ex)
            {
                log.Error("Error getting package {0}: {1}", name, ex);
                throw;
            }
        }

        public NGinnBPM.Lib.Schema.ProcessDefinition GetProcess(string name)
        {
            try
            {
                string pkg, proc, v;
                PackageDefinition.SplitDefinitionId(name, out pkg, out proc, out v);
                IProcessPackageStore pst = GetPackageStore(pkg);
                
                PackageDefinition pd = GetPackage(pkg);
				if (pd == null) throw new NGinnBPM.Lib.Exceptions.NGinnException("Package not found: " + pkg).SetPermanent(true);
                ProcessDefinition def = pst.GetProcessDefinition(v == null ? proc : string.Format("{0}.{1}", proc, v));
                def.Package = pd;
                return def;
            }
            catch (Exception ex)
            {
                log.Error("Error loading process {0}: {1}", name, ex);
                throw;
            }
        }

        #endregion


        private IProcessPackageStore LoadPackageFile(string fileName)
        {
            DateTime st = DateTime.Now;
            XmlProcessPackageStore xps = new XmlProcessPackageStore(fileName);
            PackageDefinition pd = xps.GetPackageDefinition();
            log.Info("Successfully loaded package {0} from {1}", pd.Name, fileName);
            //xps.ProcessReload += new XmlProcessPackageStore.ProcessReloadingDelegate(xps_ProcessReload);
            if (ScriptManager != null)
            {
                ScriptManager.PackageDefinitionUpdated(pd);
            }
            log.Info("Successfully loaded package {0} from {1}. Time: {2}", pd.Name, fileName, DateTime.Now - st);
            return xps;
        }

        private void ScanForPackages()
        {
            lock (this)
            {
                log.Info("Scanning directory {0} for packages", BaseDirectory);
                Dictionary<string, IProcessPackageStore> names = new Dictionary<string, IProcessPackageStore>();
                
                if (Directory.Exists(BaseDirectory))
                {
                    string[] subdirs = Directory.GetDirectories(BaseDirectory);
                    foreach (string sd in subdirs)
                    {
                        string subdirName = Path.GetFileNameWithoutExtension(sd);
                        if (subdirName != null && subdirName.Length > 0)
                        {
                            string pkgFilePath = Path.Combine(sd, subdirName + ".ngpk");
                            if (!File.Exists(pkgFilePath))
                            {
                                log.Info("Package {0} not found in directory {1}. Skipping", pkgFilePath, sd);
                                continue;
                            }
                            string rfPath = Path.Combine(sd, "reload.me");
                            if (File.Exists(rfPath))
                            {
                                try { File.Delete(rfPath); }
                                catch (Exception) { }
                            }
                            log.Info("Process package found in {0}", pkgFilePath);
                            try
                            {
                                DateTime st = DateTime.Now;
                                IProcessPackageStore ips = LoadPackageFile(pkgFilePath);
                                PackageDefinition pd = ips.GetPackageDefinition();

                                log.Info("Successfully loaded package {0} from {1}", pd.Name, pkgFilePath);
                                names[pd.Name] = ips;
                                log.Info("Successfully loaded package {0} from {1}. Time: {2}", pd.Name, pkgFilePath, DateTime.Now - st);
                                if (MessageBus != null)
                                {
                                    Runtime.Messages.PackageDefinitionLoaded pdl = new NGinnBPM.Runtime.Messages.PackageDefinitionLoaded();
                                    pdl.PackageName = pd.Name;
                                    MessageBus.Notify(pdl);
                                }
                            }
                            catch (Exception ex)
                            {
                                log.Error("Error reading package file: {0}: {1}", pkgFilePath, ex);
                                DiagnosticEvent de = new DiagnosticEvent { Source = "FSProcessPackageRepository", Message = string.Format("Error reading package {0}: {1}", pkgFilePath, ex.Message), Category = DiagnosticEvent.EventCategory.Error };
                                MessageBus.Notify(de);
                            }
                        }
                    }
                }
                _packageStores = names;
            }
        }




        #region IProcessPackageRepository Members


        public void ReloadPackage(string name)
        {
            lock (this)
            {
                string fn = Path.Combine(BaseDirectory, string.Format("{0}/{0}.ngpk", name));
                if (!File.Exists(fn))
                {
                    log.Warn("Reload: package file does not exist: {0}", fn);
                    return;
                }
                IProcessPackageStore ips = LoadPackageFile(fn);
                PackageDefinition pd = ips.GetPackageDefinition();
                log.Info("Successfully loaded package {0} from {1}", pd.Name, fn);
                _packageStores[pd.Name] = ips;
                if (MessageBus != null)
                {
                    Runtime.Messages.PackageDefinitionLoaded pdl = new NGinnBPM.Runtime.Messages.PackageDefinitionLoaded();
                    pdl.PackageName = pd.Name;
                    MessageBus.Notify(pdl);
                }
                
            }
        }

        #endregion

        #region IProcessPackageRepository Members


        public byte[] GetPackageResource(string pkg, string name)
        {
            IProcessPackageStore store = GetPackageStore(pkg);
            using (Stream stm = store.GetPackageContentStream(name))
            {
                MemoryStream ms = new MemoryStream();
                byte[] buf = new byte[8000];
                int n;
                while((n = stm.Read(buf, 0, buf.Length)) > 0)
                {
                    ms.Write(buf, 0, n);
                }
                return ms.GetBuffer();
            }
        }

        #endregion
    }
}
